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 * Lazy map of per-code severity overrides. Allocated only when {@link 208 * #setBindingSeverityFor(BindingDiagnosticCode, BindingDiagnosticSeverity)} 209 * is first called so that the default-off path stays allocation-free. 210 */ 211 private EnumMap<BindingDiagnosticCode, BindingDiagnosticSeverity> bindingSeverityOverrides; 212 213 public TSQLResolverConfig() { 214 // Default configuration 215 } 216 217 public INameMatcher getNameMatcher() { 218 return nameMatcher; 219 } 220 221 public void setNameMatcher(INameMatcher nameMatcher) { 222 if (nameMatcher == null) { 223 throw new IllegalArgumentException("Name matcher cannot be null"); 224 } 225 this.nameMatcher = nameMatcher; 226 } 227 228 public boolean isLegacyCompatibilityEnabled() { 229 return legacyCompatibilityEnabled; 230 } 231 232 public void setLegacyCompatibilityEnabled(boolean enabled) { 233 this.legacyCompatibilityEnabled = enabled; 234 } 235 236 public double getLegacySyncMinConfidence() { 237 return legacySyncMinConfidence; 238 } 239 240 public void setLegacySyncMinConfidence(double threshold) { 241 if (threshold < 0.0 || threshold > 1.0) { 242 throw new IllegalArgumentException("Confidence threshold must be in [0.0, 1.0]"); 243 } 244 this.legacySyncMinConfidence = threshold; 245 } 246 247 public int getMaxIterations() { 248 return maxIterations; 249 } 250 251 public void setMaxIterations(int maxIterations) { 252 if (maxIterations < 1) { 253 throw new IllegalArgumentException("Max iterations must be at least 1"); 254 } 255 this.maxIterations = maxIterations; 256 } 257 258 public double getMinProgressRate() { 259 return minProgressRate; 260 } 261 262 public void setMinProgressRate(double minProgressRate) { 263 if (minProgressRate < 0.0 || minProgressRate > 1.0) { 264 throw new IllegalArgumentException("Progress rate must be in [0.0, 1.0]"); 265 } 266 this.minProgressRate = minProgressRate; 267 } 268 269 public int getStablePassesForConvergence() { 270 return stablePassesForConvergence; 271 } 272 273 public void setStablePassesForConvergence(int stablePasses) { 274 if (stablePasses < 1) { 275 throw new IllegalArgumentException("Stable passes must be at least 1"); 276 } 277 this.stablePassesForConvergence = stablePasses; 278 } 279 280 public boolean isCollectFullCandidates() { 281 return collectFullCandidates; 282 } 283 284 public void setCollectFullCandidates(boolean collectFullCandidates) { 285 this.collectFullCandidates = collectFullCandidates; 286 } 287 288 /** 289 * @deprecated Use NamespaceEnhancer instead 290 */ 291 public boolean isEvidenceCollectionEnabled() { 292 return evidenceCollectionEnabled; 293 } 294 295 /** 296 * @deprecated Use NamespaceEnhancer instead 297 */ 298 public void setEvidenceCollectionEnabled(boolean enabled) { 299 this.evidenceCollectionEnabled = enabled; 300 } 301 302 /** 303 * Check if datatype information should be shown for columns from CREATE TABLE statements. 304 * 305 * @return true if datatype information should be included in column names 306 */ 307 public boolean isShowDatatype() { 308 return showDatatype; 309 } 310 311 /** 312 * Set whether to show datatype information for columns from CREATE TABLE statements. 313 * When enabled, columns from CREATE TABLE will include datatype info in format: 314 * columnName:datatypeName:length or columnName:datatypeName:precision:scale 315 * 316 * @param showDatatype true to include datatype information 317 */ 318 public void setShowDatatype(boolean showDatatype) { 319 this.showDatatype = showDatatype; 320 } 321 322 /** 323 * Check if CTE (Common Table Expression) tables and columns should be shown in output. 324 * 325 * @return true if CTE tables and columns should be included 326 */ 327 public boolean isShowCTE() { 328 return showCTE; 329 } 330 331 /** 332 * Set whether to show CTE (Common Table Expression) tables and columns in output. 333 * When enabled, CTE tables are included in the tables list and CTE columns 334 * are included in the fields list with "(CTE)" suffix. 335 * 336 * @param showCTE true to include CTE tables and columns 337 */ 338 public void setShowCTE(boolean showCTE) { 339 this.showCTE = showCTE; 340 } 341 342 // ========== Display Name Getters and Setters ========== 343 344 /** 345 * Get the display name mode. 346 * 347 * @return the current display name mode 348 */ 349 public DisplayNameMode getDisplayNameMode() { 350 return displayNameMode; 351 } 352 353 /** 354 * Set the display name mode. 355 * 356 * @param mode the display name mode 357 */ 358 public void setDisplayNameMode(DisplayNameMode mode) { 359 this.displayNameMode = mode != null ? mode : DisplayNameMode.DISPLAY; 360 } 361 362 /** 363 * Get the display name policy. 364 * 365 * @return the current display name policy 366 */ 367 public DisplayNamePolicy getDisplayNamePolicy() { 368 return displayNamePolicy; 369 } 370 371 /** 372 * Set the display name policy. 373 * 374 * @param policy the display name policy 375 */ 376 public void setDisplayNamePolicy(DisplayNamePolicy policy) { 377 this.displayNamePolicy = policy != null ? policy : DisplayNamePolicy.PREFER_DEFINITION_SITE; 378 } 379 380 /** 381 * Check if delimiters should be stripped for display. 382 * 383 * @return true if delimiters should be stripped 384 */ 385 public boolean isStripDelimitersForDisplay() { 386 return stripDelimitersForDisplay; 387 } 388 389 /** 390 * Set whether to strip delimiters for display. 391 * 392 * @param stripDelimiters true to strip delimiters 393 */ 394 public void setStripDelimitersForDisplay(boolean stripDelimiters) { 395 this.stripDelimitersForDisplay = stripDelimiters; 396 } 397 398 /** 399 * Get the strategy for handling ambiguous columns. 400 * Returns the configured value, or TBaseType.GUESS_COLUMN_STRATEGY if not set. 401 * 402 * @return One of GUESS_COLUMN_STRATEGY_NEAREST, GUESS_COLUMN_STRATEGY_FARTHEST, 403 * or GUESS_COLUMN_STRATEGY_NOT_PICKUP 404 */ 405 public int getGuessColumnStrategy() { 406 return guessColumnStrategy != null ? guessColumnStrategy : TBaseType.GUESS_COLUMN_STRATEGY; 407 } 408 409 /** 410 * Set the strategy for handling ambiguous columns. 411 * 412 * @param strategy One of GUESS_COLUMN_STRATEGY_NEAREST, GUESS_COLUMN_STRATEGY_FARTHEST, 413 * or GUESS_COLUMN_STRATEGY_NOT_PICKUP 414 */ 415 public void setGuessColumnStrategy(int strategy) { 416 if (strategy < GUESS_COLUMN_STRATEGY_NEAREST || strategy > GUESS_COLUMN_STRATEGY_NOT_PICKUP) { 417 throw new IllegalArgumentException("Invalid strategy: " + strategy + 418 ". Must be GUESS_COLUMN_STRATEGY_NEAREST (0), GUESS_COLUMN_STRATEGY_FARTHEST (1), " + 419 "or GUESS_COLUMN_STRATEGY_NOT_PICKUP (2)"); 420 } 421 this.guessColumnStrategy = strategy; 422 } 423 424 /** 425 * Check if a custom guess column strategy has been set on this config. 426 * If false, the strategy from TBaseType.GUESS_COLUMN_STRATEGY will be used. 427 * 428 * @return true if a custom strategy is set 429 */ 430 public boolean hasCustomGuessColumnStrategy() { 431 return guessColumnStrategy != null; 432 } 433 434 /** 435 * Clear any custom guess column strategy, reverting to TBaseType.GUESS_COLUMN_STRATEGY. 436 */ 437 public void clearGuessColumnStrategy() { 438 this.guessColumnStrategy = null; 439 } 440 441 /** 442 * Get the human-readable name for the current strategy. 443 * 444 * @return Strategy name (e.g., "GUESS_COLUMN_STRATEGY_NEAREST") 445 */ 446 public String getGuessColumnStrategyName() { 447 int strategy = getGuessColumnStrategy(); 448 if (strategy >= 0 && strategy < GUESS_COLUMN_STRATEGY_NAMES.length) { 449 return GUESS_COLUMN_STRATEGY_NAMES[strategy]; 450 } 451 return "UNKNOWN(" + strategy + ")"; 452 } 453 454 // ========== Confidence Threshold Getters and Setters ========== 455 456 /** 457 * Get the minimum confidence threshold for definite resolutions. 458 * 459 * @return Threshold value [0.0, 1.0] 460 */ 461 public double getMinDefiniteConfidence() { 462 return minDefiniteConfidence; 463 } 464 465 /** 466 * Set the minimum confidence threshold for definite resolutions. 467 * 468 * @param threshold Threshold value [0.0, 1.0] 469 */ 470 public void setMinDefiniteConfidence(double threshold) { 471 if (threshold < 0.0 || threshold > 1.0) { 472 throw new IllegalArgumentException("Confidence threshold must be in [0.0, 1.0]"); 473 } 474 this.minDefiniteConfidence = threshold; 475 } 476 477 /** 478 * Get the minimum confidence threshold to allow guessing. 479 * 480 * @return Threshold value [0.0, 1.0] 481 */ 482 public double getMinConfidenceToGuess() { 483 return minConfidenceToGuess; 484 } 485 486 /** 487 * Set the minimum confidence threshold to allow guessing. 488 * 489 * @param threshold Threshold value [0.0, 1.0] 490 */ 491 public void setMinConfidenceToGuess(double threshold) { 492 if (threshold < 0.0 || threshold > 1.0) { 493 throw new IllegalArgumentException("Confidence threshold must be in [0.0, 1.0]"); 494 } 495 this.minConfidenceToGuess = threshold; 496 } 497 498 /** 499 * Check if guessing is allowed when all candidates are inferred. 500 * 501 * @return true if guessing is allowed with inferred candidates 502 */ 503 public boolean isAllowGuessWhenAllInferred() { 504 return allowGuessWhenAllInferred; 505 } 506 507 /** 508 * Set whether to allow guessing when all candidates are inferred. 509 * 510 * @param allow true to allow guessing with inferred candidates 511 */ 512 public void setAllowGuessWhenAllInferred(boolean allow) { 513 this.allowGuessWhenAllInferred = allow; 514 } 515 516 /** 517 * Check if a confidence value represents a definite resolution. 518 * 519 * @param confidence The confidence value to check 520 * @return true if the confidence is >= minDefiniteConfidence 521 */ 522 public boolean isDefiniteConfidence(double confidence) { 523 return confidence >= minDefiniteConfidence; 524 } 525 526 /** 527 * Check if a confidence value is sufficient to allow guessing. 528 * 529 * @param confidence The confidence value to check 530 * @return true if the confidence is >= minConfidenceToGuess 531 */ 532 public boolean canGuessWithConfidence(double confidence) { 533 return confidence >= minConfidenceToGuess; 534 } 535 536 // ========== Binding Diagnostic Getters and Setters (plan §5.3, S2) ========== 537 538 /** 539 * @return whether the binding-diagnostic post-pass is enabled 540 */ 541 public boolean isEmitBindingDiagnostics() { 542 return emitBindingDiagnostics; 543 } 544 545 /** 546 * Enable or disable the binding-diagnostic post-pass. 547 * 548 * <p>Default {@code false}. When off, the post-pass is not invoked and 549 * accessors return {@link 550 * gudusoft.gsqlparser.resolver2.binding.BindingResult#empty()}.</p> 551 * 552 * @param on whether to emit binding diagnostics 553 * @return this config (fluent) 554 */ 555 public TSQLResolverConfig setEmitBindingDiagnostics(boolean on) { 556 this.emitBindingDiagnostics = on; 557 return this; 558 } 559 560 /** 561 * @return whether strict catalog validation is enabled 562 */ 563 public boolean isBindingStrictCatalogValidation() { 564 return bindingStrictCatalogValidation; 565 } 566 567 /** 568 * Enable or disable strict catalog validation. Default {@code false}. 569 * 570 * @param on whether to enable strict mode 571 * @return this config (fluent) 572 */ 573 public TSQLResolverConfig setBindingStrictCatalogValidation(boolean on) { 574 this.bindingStrictCatalogValidation = on; 575 return this; 576 } 577 578 /** 579 * @return whether successful binding references are included in the 580 * result payload 581 */ 582 public boolean isBindingIncludeSuccessfulReferences() { 583 return bindingIncludeSuccessfulReferences; 584 } 585 586 /** 587 * Enable or disable successful-reference emission. Default {@code false} 588 * (payload bloat for non-IDE consumers). 589 * 590 * @param on whether to include successful references 591 * @return this config (fluent) 592 */ 593 public TSQLResolverConfig setBindingIncludeSuccessfulReferences(boolean on) { 594 this.bindingIncludeSuccessfulReferences = on; 595 return this; 596 } 597 598 /** 599 * Override the severity emitted for a given binding diagnostic code. 600 * 601 * <p>The override map is allocated lazily so callers that never customize 602 * severities pay no allocation cost.</p> 603 * 604 * @param code the diagnostic code (must not be null) 605 * @param severity the severity to emit for this code (must not be null) 606 * @return this config (fluent) 607 * @throws IllegalArgumentException if either argument is null 608 */ 609 public TSQLResolverConfig setBindingSeverityFor(BindingDiagnosticCode code, 610 BindingDiagnosticSeverity severity) { 611 if (code == null) { 612 throw new IllegalArgumentException("BindingDiagnosticCode is required"); 613 } 614 if (severity == null) { 615 throw new IllegalArgumentException("BindingDiagnosticSeverity is required"); 616 } 617 if (bindingSeverityOverrides == null) { 618 bindingSeverityOverrides = new EnumMap<BindingDiagnosticCode, BindingDiagnosticSeverity>( 619 BindingDiagnosticCode.class); 620 } 621 bindingSeverityOverrides.put(code, severity); 622 return this; 623 } 624 625 /** 626 * Resolve the configured severity for a diagnostic code. Returns the 627 * override when one is set, otherwise the code's frozen default 628 * severity (plan §5.4). 629 * 630 * @param code the diagnostic code (must not be null) 631 * @return the configured or default severity for this code 632 */ 633 public BindingDiagnosticSeverity getBindingSeverityFor(BindingDiagnosticCode code) { 634 if (code == null) { 635 throw new IllegalArgumentException("BindingDiagnosticCode is required"); 636 } 637 if (bindingSeverityOverrides != null) { 638 BindingDiagnosticSeverity override = bindingSeverityOverrides.get(code); 639 if (override != null) { 640 return override; 641 } 642 } 643 return code.defaultSeverity(); 644 } 645 646 /** 647 * @return whether any per-code severity override has been configured. 648 * Used to keep the default-off path allocation-free in tests. 649 */ 650 public boolean hasBindingSeverityOverrides() { 651 return bindingSeverityOverrides != null && !bindingSeverityOverrides.isEmpty(); 652 } 653 654 /** 655 * @return an immutable view of the configured severity overrides, or an 656 * empty map when none are configured. Never null. 657 */ 658 public Map<BindingDiagnosticCode, BindingDiagnosticSeverity> getBindingSeverityOverrides() { 659 if (bindingSeverityOverrides == null || bindingSeverityOverrides.isEmpty()) { 660 return java.util.Collections.<BindingDiagnosticCode, BindingDiagnosticSeverity>emptyMap(); 661 } 662 return java.util.Collections.unmodifiableMap(bindingSeverityOverrides); 663 } 664 665 /** 666 * Create a default configuration 667 */ 668 public static TSQLResolverConfig createDefault() { 669 return new TSQLResolverConfig(); 670 } 671 672 /** 673 * Create configuration for case-sensitive matching 674 */ 675 public static TSQLResolverConfig createCaseSensitive() { 676 TSQLResolverConfig config = new TSQLResolverConfig(); 677 config.setNameMatcher(new DefaultNameMatcher(true)); 678 return config; 679 } 680 681 /** 682 * Create configuration for standalone mode (no legacy sync) 683 */ 684 public static TSQLResolverConfig createStandalone() { 685 TSQLResolverConfig config = new TSQLResolverConfig(); 686 config.setLegacyCompatibilityEnabled(false); 687 return config; 688 } 689 690 /** 691 * Create configuration with showDatatype enabled. 692 * This configuration includes datatype information for columns from CREATE TABLE statements. 693 */ 694 public static TSQLResolverConfig createWithDatatype() { 695 TSQLResolverConfig config = new TSQLResolverConfig(); 696 config.setShowDatatype(true); 697 return config; 698 } 699 700 /** 701 * Create configuration with showCTE enabled. 702 * This configuration includes CTE tables and columns in the output. 703 */ 704 public static TSQLResolverConfig createWithCTE() { 705 TSQLResolverConfig config = new TSQLResolverConfig(); 706 config.setShowCTE(true); 707 return config; 708 } 709 710 /** 711 * Create configuration for a specific database vendor. 712 * 713 * <p>This factory method creates a configuration with vendor-specific 714 * name matching rules. The VendorNameMatcher uses IdentifierService 715 * to properly handle case sensitivity and quote handling for each vendor.</p> 716 * 717 * <p>Example vendor behaviors:</p> 718 * <ul> 719 * <li>Oracle: Unquoted identifiers fold to UPPER, quoted are case-sensitive</li> 720 * <li>PostgreSQL: Unquoted identifiers fold to LOWER, quoted are case-sensitive</li> 721 * <li>MySQL: Depends on lower_case_table_names setting</li> 722 * <li>BigQuery: Table names are case-sensitive, column names are case-insensitive</li> 723 * </ul> 724 * 725 * @param vendor the database vendor 726 * @return configuration with vendor-specific name matcher 727 */ 728 public static TSQLResolverConfig createForVendor(EDbVendor vendor) { 729 TSQLResolverConfig config = new TSQLResolverConfig(); 730 config.vendor = vendor; 731 config.nameMatcher = new VendorNameMatcher(vendor); 732 return config; 733 } 734 735 /** 736 * Create configuration for a specific database vendor with datatype display enabled. 737 * 738 * @param vendor the database vendor 739 * @return configuration with vendor-specific name matcher and datatype display 740 */ 741 public static TSQLResolverConfig createForVendorWithDatatype(EDbVendor vendor) { 742 TSQLResolverConfig config = createForVendor(vendor); 743 config.setShowDatatype(true); 744 return config; 745 } 746 747 /** 748 * Get the database vendor, if set. 749 * 750 * @return the database vendor, or null if not set 751 */ 752 public EDbVendor getVendor() { 753 return vendor; 754 } 755 756 /** 757 * Set the database vendor and update name matcher accordingly. 758 * 759 * @param vendor the database vendor 760 */ 761 public void setVendor(EDbVendor vendor) { 762 this.vendor = vendor; 763 if (vendor != null) { 764 this.nameMatcher = new VendorNameMatcher(vendor); 765 } 766 } 767 768 /** 769 * Check if vendor-specific name matching is enabled. 770 * 771 * @return true if a vendor is configured 772 */ 773 public boolean hasVendor() { 774 return vendor != null; 775 } 776 777 @Override 778 public String toString() { 779 return String.format( 780 "TSQLResolverConfig{vendor=%s, nameMatcher=%s, legacyCompat=%s, minConfidence=%.2f, maxIter=%d, guessStrategy=%s, " + 781 "minDefiniteConf=%.2f, minGuessConf=%.2f, allowGuessInferred=%s, showDatatype=%s, showCTE=%s, displayMode=%s, displayPolicy=%s}", 782 vendor, 783 nameMatcher, 784 legacyCompatibilityEnabled, 785 legacySyncMinConfidence, 786 maxIterations, 787 getGuessColumnStrategyName(), 788 minDefiniteConfidence, 789 minConfidenceToGuess, 790 allowGuessWhenAllInferred, 791 showDatatype, 792 showCTE, 793 displayNameMode, 794 displayNamePolicy 795 ); 796 } 797}